package learner.hmm;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;

import learner.ObservationPoint;
import learner.Pattern;

public class Movement {

	private static final double ROUND_OFF_ERROR_FIX = 0.00001;
	private List<MovementVector> vectors = new ArrayList<MovementVector>();

	public Movement(List<MovementVector> vectors) {
		this.vectors = vectors;
	}
	public Movement(Pattern givenPattern) throws IllegalArgumentException {
		if (givenPattern.getPoints() == null || givenPattern.getPoints().size() < 2)
			throw new IllegalArgumentException();
		setVectorsFromPoints(givenPattern);
	}
	
	public Movement permute() {
		return new Movement(permute(new LinkedList<MovementVector>(vectors)));
	}

	private static List<MovementVector> permute(List<MovementVector> vectors) {
		if (vectors.isEmpty()) // trivial case
			return vectors; 
		Random random = new Random();
		int index = random.nextInt(vectors.size());
		MovementVector selectedPoint = vectors.get(index);
		vectors.remove(index);
		List<MovementVector> permutation = new ArrayList<MovementVector>();
		permutation.add(selectedPoint);
		permutation.addAll(permute(new LinkedList<MovementVector>(vectors)));
		return permutation;
	}
	/**
	 * Meegegeven pattern moet wel minstens 2 punten hebben.
	 * 
	 * @param givenPattern
	 */
	private void setVectorsFromPoints(Pattern givenPattern) {
		List<ObservationPoint> points = givenPattern.getPoints();
		int previousX = points.get(0).getX();
		int previousY = points.get(0).getY();
		long previousT = points.get(0).getT();
		for (int i = 0; i < points.size() - 1; i++) {

			int newX = points.get(i + 1).getX();
			int newY = points.get(i + 1).getY();
			long newT = points.get(i + 1).getT();

			int xDiff = newX - previousX;
			int yDiff = newY - previousY;
			long tDiff = newT - previousT;

			MovementVector vector = new MovementVector(xDiff, yDiff, tDiff);
			//interpolatie
			if(vector.isStationary()){
				continue;
			}
			//dus er is even geen data doorgezonden, eventuele overgeslagen tegels liggen dus NIET
			//aan de samplefrequentie
			if(vector.getT()>45){
				vectors.add(vector);
			}
			//mogelijks zijn er tegels overgeslagen door te lage samplefrequentie
			else{
				double xDiffUpToNow=0;
				double yDiffUpToNow=0;
				double tDiffUpToNow=0;
				int lengthInt = (int)vector.getLength();
				for(int j =1;j<=lengthInt;j++){
					xDiffUpToNow+=(double)xDiff/lengthInt;
					yDiffUpToNow+=(double)yDiff/lengthInt;
					tDiffUpToNow+=(double)tDiff/lengthInt;
					xDiffUpToNow+= getRoundOffErrorFix(xDiffUpToNow);
					yDiffUpToNow+= getRoundOffErrorFix(yDiffUpToNow);
					tDiffUpToNow+= getRoundOffErrorFix(tDiffUpToNow);
					MovementVector move=new MovementVector((int) xDiffUpToNow, (int) yDiffUpToNow, (int) tDiffUpToNow);
					if(!move.isStationary()){
						vectors.add(move);
					}
					xDiffUpToNow=xDiffUpToNow%1;
					yDiffUpToNow=yDiffUpToNow%1;
					tDiffUpToNow=tDiffUpToNow%1;
				}
			}
			previousX = newX;
			previousY = newY;
			previousT = newT;
		}
	}

	private double getRoundOffErrorFix(double num) {
		if(num>0)
			return ROUND_OFF_ERROR_FIX;
		else
			return -ROUND_OFF_ERROR_FIX;
	}
	public List<MovementVector> getMovementVectors() {
		return new ArrayList<MovementVector>(vectors);
	}

	@Override
	public String toString() {
		return "Movements [vectors=" + vectors + "]";
	}

	public double getRelativeFrequencyOf(MovementVector movement) {
		int frequency = 0;
		for (MovementVector vector : vectors) {
			if (vector.equals(movement)) {
				frequency++;
			}
		}
		return (double) frequency / (double) getSize();
	}

	public int getSize() {
		return vectors.size();
	}

	public Set<MovementVector> getDifferentMovements() {
		return new HashSet<MovementVector>(vectors);
	}

	/**
	 * Time goes from 1 to length of movement
	 * 
	 * @param k
	 * @return
	 */
	public MovementVector getMovementForTime(int k) {
		if (k < 1 || k > vectors.size())
			throw new IllegalArgumentException(
					"k must go from 1 to length of movement. k= " + k);
		return vectors.get(k - 1);
	}

	public List<Map<MovementVector, Double>> getRelativeFreqMovementIntervals(int nbOfIntervals) {
		List<Map<MovementVector, Double>> intervals = new ArrayList<Map<MovementVector, Double>>();
		List<Integer> sizeOfIntervals = calculateSizeOfIntervals(nbOfIntervals);
		int startInterval = 1;
		for (int intervalIndex = 0 ; intervalIndex < sizeOfIntervals.size() ; intervalIndex ++) {
			int endInterval = startInterval + sizeOfIntervals.get(intervalIndex); // left inclusive, right exclusive
			intervals.add(getRelativeFreqMovement(startInterval, endInterval ));
			startInterval = endInterval;
		}
		return intervals;
	}
	private Map<MovementVector, Double> getRelativeFreqMovement(
			int startInterval, int endInterval) {
		Map<MovementVector, Double> freqs = new HashMap<MovementVector, Double>();
		for (MovementVector movement : getDifferentMovements()) {
			double nbOfMovementsInInterval = endInterval - startInterval;
			double freq = (double)count(movement, startInterval, endInterval) / nbOfMovementsInInterval;
			freqs.put(movement, freq);
		}
		return freqs;

	}
	private int count(MovementVector movement, int startInterval,
			int endInterval) {
		int count = 0;
		for (int i = startInterval - 1 ; i < endInterval - 1 ; i ++) {
			if (vectors.get(i).equals(movement))
				count ++;
		}
		return count;
	}
	private List<Integer> calculateSizeOfIntervals(int nbOfIntervals) {
		List<Integer> sizeOfIntervals = new ArrayList<Integer>();
		int smallestIntervalSize = vectors.size() / nbOfIntervals;
		int nbOfBiggerIntervals = vectors.size() % nbOfIntervals;
		for (int i = 0 ; i < nbOfIntervals ; i ++ ) {
			if (i < nbOfBiggerIntervals)
				sizeOfIntervals.add(smallestIntervalSize + 1);
			else
				sizeOfIntervals.add(smallestIntervalSize);
		}
		return sizeOfIntervals;
	}
}
